/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 */

import process from 'process';
import { io, ManagerOptions, Socket, SocketOptions } from 'socket.io-client';

import { DAEMON_SERVER_LOCAL_HOST, DEFAULT_DAEMON_REGISTRY_PATH } from '../const/daemon-const.js';
import { HvigorDaemonClientLogger } from '../log/hvigor-daemon-client-log.js';
import { defaultDaemonServerFactory } from '../process/daemon-process-factory.js';
import { defaultDaemonClientRegistry } from '../registry/daemon-client-registry.js';
import { DaemonInfo, DaemonState } from '../registry/daemon-info.js';
import { SessionIdHelper } from '../session/session-id.js';

import { DaemonConnector } from './daemon-connector.js';

const log = HvigorDaemonClientLogger.getLogger('daemon-client');

const SocketEvent = {
  EVENT_CONNECT: 'connect',
  EVENT_DISCONNECT: 'disconnect',
  EVENT_CONNECT_ERROR: 'connect_error',
};

/**
 * 为client获取可用的与daemon的socket连接
 */
export class DefaultDaemonConnector implements DaemonConnector {
  async getDaemonConnection(): Promise<Socket | undefined> {
    if (defaultDaemonServerFactory.createDaemonPromise !== undefined) {
      await defaultDaemonServerFactory.createDaemonPromise;
    }
    const idleDaemonInfo: DaemonInfo | undefined = await defaultDaemonServerFactory.getProjectCompatibleIdleDaemon();
    if (idleDaemonInfo === undefined) {
      return undefined;
    }
    try {
      // 用来判断需要建立连接的daemon process是否存在,
      // 后续考虑在daemon process中增加一个监听用来询问该pid对应的process是否为hvigor的daemon process
      // 考虑使用node-addon，借用libuv的能力来实现
      process.kill(idleDaemonInfo.pid, 0);
    } catch (e) {
      // 通过注册表查找到的信息不合法,比如找到的信息中对应的pid的进程在系统中不存在
      log.debug('hvigor client: Registry info is incorrect: Cannot find a valid process by process id.');
      const daemonProcessStateChangeInfo = 'process is broken by user or operating system.';

      // 由client侧直接更新注册表信息,设置该daemon为broken状态
      defaultDaemonClientRegistry.updateDaemonProcessState(idleDaemonInfo, DaemonState.BROKEN, daemonProcessStateChangeInfo);
      return this.getDaemonConnection();
    }

    // 在客户端提前拦截,如果发现当前daemon process已经处于拒绝请求或者busy状态,则直接报错,建议稍后重试
    if (idleDaemonInfo.state === DaemonState.BUSY || idleDaemonInfo.state === DaemonState.STOP_REQ) {
      const errMsg =
        'hvigor client: Current daemon process is busy, cannot accept new build request. ' +
        'You can run hvigorw --status-daemon to check daemon status, or rerun build with --no-daemon param. ' +
        `Delete ${DEFAULT_DAEMON_REGISTRY_PATH} is also a solution, but this might stop current build action.`;

      // 当前处于client侧进程,如果抛出异常,则会直接终止client侧的后续动作,也不会起daemon
      log.error(errMsg);
      log.clientErrorLink();
      process.exit(-1);
    }

    // 启动connect前设置默认exitCode，如果正常执行完毕，会在BuildStatus事件处理中更新exitCode
    process.exitCode = -1;

    const opts: Partial<ManagerOptions & SocketOptions> = {
      transports: ['websocket'],
    };
    if (idleDaemonInfo.sessionId) {
      const sessionId = SessionIdHelper.decryptPwdByDefaultMaterial(idleDaemonInfo.sessionId, log);
      opts.path = `/${sessionId}`;
    }

    log.debug(`hvigor client: connect to hvigor daemon, daemonInfo=${JSON.stringify(idleDaemonInfo)}`);
    const connection = io(`ws://${DAEMON_SERVER_LOCAL_HOST}:${idleDaemonInfo.port}`, opts);
    connection.on(SocketEvent.EVENT_CONNECT, this.onConnect.bind(this));
    connection.on(SocketEvent.EVENT_DISCONNECT, this.onClose.bind(this, idleDaemonInfo));
    connection.on(SocketEvent.EVENT_CONNECT_ERROR, this.onConnectError.bind(this, idleDaemonInfo));
    return connection;
  }

  onConnect(...args: any[]): void {
    log.debug('hvigor client: Client connected.');
  }

  onClose(daemonInfo: DaemonInfo, reason: string): void {
    log.debug('hvigor client: Connection between client and daemon is disconnected:', reason);
    switch (reason) {
      case 'ping timeout':
      case 'transport error':
      case 'transport close':
        try {
          process.kill(daemonInfo.pid, 0);
        } catch (e) {
          log.error('hvigor client: This is an error caused by hvigor daemon. The daemon process was stopped.');
          log.daemonErrorLink(daemonInfo.pid);
          break;
        }
        log.error(
          'hvigor client: This is an error caused by hvigor daemon. ' +
            `The connection between hvigor client and build daemon is disconnected. Reason: ${reason}`,
        );
        log.daemonErrorLink(daemonInfo.pid);
    }
    process.exit();
  }

  onConnectError(daemonInfo: DaemonInfo, error: Error): void {
    log.error('hvigor client: Connection between client and daemon is disconnected with a connect error:', error);
    log.error('hvigor client: This is an error caused by hvigor daemon.');
    log.daemonErrorLink(daemonInfo.pid);
    process.exit();
  }
}
